﻿#include "PrecompiledHeader.h"
#include "ViaMain.h"

/*--------------------------------------------------------------------------*/

bool ViaMain::m_isLoopActive = true;
FILE* ViaMain::m_log = NULL;

bool ViaMain::m_benchmarkEnabled = false;
bool ViaMain::m_benchmarkNextFrame = false;
Timer ViaMain::m_benchmarkTimer = Timer();
ICriticalSection* ViaMain::m_benchmarkCriticalSection = NULL;

std::vector<IEngMod*> ViaMain::m_modulesPtnVec = std::vector<IEngMod*>();
std::vector<struct ViaMain::FunctionCall> ViaMain::m_functionsOrder = std::vector<struct FunctionCall>();

/*--------------------------------------------------------------------------*/

ViaMain::ViaMain()
{
}

/*--------------------------------------------------------------------------*/

ViaMain::~ViaMain()
{
}

/*--------------------------------------------------------------------------*/

void ViaMain::Initialize()
{
	if (m_log == NULL)
	{
		m_log = fopen("../Binaries/log.html", "w");

		if (m_log)
		{
			fprintf(m_log, LOG_HTML_BEGIN);
		}
	}

#ifdef _DEBUG
	AssertLogI(true, "RUNNING IN DEBUG MODE");
#else
	AssertLogI(true, "RUNNING IN RELEASE MODE");
#endif

	AssertLogC(m_log == NULL, "Can not open log.html file.");
	AssertLogC(m_isLoopActive == false, "ViaMain already initialized?");

	/*--System------------------------------------------------------------------*/
	FileSys::Init();
	FileSys::AddSource("../Binaries");

	g_Settings = new CSettings();	// wczytanie ustawien
	Init(g_Settings);				// nic

	g_Shell = new CShell();			// konsola, inicjalizacja LUA

	g_Sdl = new CSdl();				// tworzenie okna, inicjalizacja pluginów SDL
	Init(g_Sdl);					// nic

	ROpenGL::Init(g_Settings->GetResolutioni());	// inicjalizacja OpenGL

	FileSys::AddSource("../Data");

	/*--klasy SDL-o pochodne----------------------------------------------------*/
	g_Calc = new CCalc();			// bindowanie LUA
	g_Mouse = new CMouse();			// nic
	g_Keyboard = new CKeyboard();	// bindowanie LUA
	g_Mixer = new CMixer();

	Init(g_Calc);					// startowanie wątków
	Init(g_Shell);					// bindowanie ICalc
	Init(g_Mixer);					// nic
	Init(g_Keyboard);				// nic
	Init(g_Mouse);					// nic

	g_Renderer = new CRenderer();	// inicjalizacja renderqueue
	Init(g_Renderer);				// nic
	g_Renderer->ShowSplashscreen(ESplashscreen::WindowTitle, "Renderer initialized");

	/*--------------------------------------------------------------------------*/
	g_Resources = new CResources();	// bindowanie LUA
	Init(g_Resources);				// nic

	g_Resources->LoadResources("../Data/resources.xml");
	g_Renderer->ShowSplashscreen(ESplashscreen::WindowTitle, "Resources initialized");

	g_Material = new CMaterial();	// bindowanie LUA, UBO
	Init(g_Material);				// nic

	g_Material->LoadMaterials("../Data/materials.xml");
	g_Renderer->ShowSplashscreen(ESplashscreen::WindowTitle, "Materials loaded");

	/*--------------------------------------------------------------------------*/

	g_Hud = new CHud();
	Init(g_Hud);

	/*--------------------------------------------------------------------------*/

	if (g_Settings->GetSplashscreenEnabled())
	{
		TextureRes* splashscreen = NULL;
		g_Resources->Get("splashscreen", &splashscreen);
		g_Renderer->ShowSplashscreen(ESplashscreen::Text_Image, "ViaLactea Engine splashscreen. See ViaMain.cpp for more details", splashscreen->GetTexture());
		//g_Sdl->Delay(1000);
	}

	/*--------------------------------------------------------------------------*/

	g_Scene = new CScene();
	Init(g_Scene);

	/*--------------------------------------------------------------------------*/

	ViaMain::m_benchmarkCriticalSection = new ICriticalSection(1);
	g_Calc->EnableMultithreading();

	AssertLogI(true, "All modules initialized correctly.");

#ifdef _DEBUG
	LogCallOnLoopEvents();
#endif
}

/*--------------------------------------------------------------------------*/

void ViaMain::Finalize()
{
	delete ViaMain::m_benchmarkCriticalSection;

	m_functionsOrder.clear();

	/*--------------------------------------------------------------------------*/

	Free(&g_Hud);
	Free(&g_Scene);
	Free(&g_Mixer);
	Free(&g_Mouse);
	Free(&g_Keyboard);
	Free(&g_Material);
	Free(&g_Renderer);
	Free(&g_Resources);
	Free(&g_Shell);
	Free(&g_Calc);
	Free(&g_Sdl);
	Free(&g_Settings);

	/*--------------------------------------------------------------------------*/

	ROpenGL::Free();

	AssertLogC(m_functionsOrder.size() != 0, "Some CallOnLoop instances left.");
	AssertLogC(m_modulesPtnVec.size() != 0, "Not all of IEngMod unregistered.");

	AssertLogI(true, "GOODBYE!");

	if (m_log != NULL)
	{
		fprintf(m_log, LOG_HTML_END);

		fclose(m_log);
		m_log = NULL;
	}
}

/*--------------------------------------------------------------------------*/

void ViaMain::Log(const char* file, int line, const char* func, const char* code, int priority, const char* format, ...)
{
	static char messageboxBuffer[4096];
	static char logfileBuffer[4096];
	static char vsdebugBuffer[4096];

	va_list args;
	va_start(args, format);
	vsprintf(logfileBuffer, format, args);
	perror(logfileBuffer);
	va_end(args);

	vsdebugBuffer[0] = 0;
	sprintf(vsdebugBuffer, "[ViaMain] >>> %s <<<\n", logfileBuffer);

	OutputDebugString(vsdebugBuffer);

	if (m_log)
	{
		if (priority == 0) //info
		{
			std::cout << "#c" << logfileBuffer << "\n";
			WriteLog(file, line, func, priority, logfileBuffer);
		}
		else if (priority == 1) //warning
		{
			std::cout << "#y" << logfileBuffer << "\n";
			WriteLog(file, line, func, priority, logfileBuffer);
		}
		else if (priority == 2) //error
		{
			std::cout << "#r" << logfileBuffer << "\n";
			WriteLog(file, line, func, priority, logfileBuffer);
			//DebugBreak();
		}
		else //critical
		{
			std::cout << "#r" << logfileBuffer << "\n";
			WriteLog(file, line, func, priority, logfileBuffer);

			sprintf(messageboxBuffer, "In file:\n%s, at line %d\n\nIn function:\n%s\n\nCondition:\n%s\n\nDescription:\n", file, line, func, code);

			strcat(messageboxBuffer, logfileBuffer);
			strcat(messageboxBuffer, "\n\nPress 'Abort' to kill application\nPress 'Retry' to break\nPress 'Ignore' to continue");

			int retVal = MessageBox(NULL, messageboxBuffer, "ViaEngine", MB_ABORTRETRYIGNORE | MB_ICONERROR);

			switch (retVal)
			{
			case IDABORT:
			{
				ViaMain::ForceExit();
				break;
			}

			case IDRETRY:
			{
				DebugBreak();
				break;
			}

			case IDIGNORE:
			{
				break;
			}
			}
		}
	}
}

/*--------------------------------------------------------------------------*/

void ViaMain::Message(const char* file, int line, const char* func, const char* format, ...)
{
	static char buffer[2048];

	va_list args;
	va_start(args, format);
	vsprintf(buffer, format, args);
	perror(buffer);
	va_end(args);

	MessageBox(NULL, buffer, "ViaLactea Engine", 0);
}

/*--------------------------------------------------------------------------*/

void ViaMain::Init(IEngMod* mod)
{
	AssertLogC(mod == NULL, "Trying to register NULL module");

	if (mod != NULL)
	{
		m_modulesPtnVec.push_back(mod);
		mod->OnInit();
	}
}

/*--------------------------------------------------------------------------*/

void ViaMain::PlayGame(IGame* game)
{
	AssertLogI(true, "VIAMAIN::INITIALIZE");
	Initialize();

	if (game->GetDataDir() != "")
	{
		FileSys::AddSource(game->GetDataDir());
	}

	g_Shell->Execute("loadscript('../Data/script/loopstart.lua')");

	if (game)
	{
		AssertLogI(true, "VIAMAIN::BEGINOFINIT");
		game->InitGame();
		AssertLogI(true, "VIAMAIN::ENDOFINIT");
	}

	FileSys::Dump();

	AssertLogI(true, "VIAMAIN::LOOP");

	while (m_isLoopActive)
	{
		if (game)
		{
			game->OnLoopTick();
		}

		Loop();
	}

	if (game)
	{
		AssertLogI(true, "VIAMAIN::DELETING_GAME");
		delete game;
	}

	AssertLogI(true, "VIAMAIN::FINALIZE");
	Finalize();
}

/*--------------------------------------------------------------------------*/

void ViaMain::CallOnLoop(IEngMod* func, uint arg, uint index, const std::string& desc)
{
	uint size = m_functionsOrder.size();

	struct FunctionCall fi;
	fi.m_mod = func;
	fi.m_index = index;
	fi.m_arg = arg;
	fi.m_description = desc;

	bool added = false;

	for (uint i = 0; i < size; ++i)
	{
		if (m_functionsOrder[i].m_index >= index)
		{
			m_functionsOrder.insert(m_functionsOrder.begin() + i, fi);
			added = true;
			break;
		}
	}

	if (!added)
	{
		m_functionsOrder.push_back(fi);
	}
}

/*--------------------------------------------------------------------------*/

void ViaMain::StopGame()
{
	m_isLoopActive = false;
}

/*--------------------------------------------------------------------------*/

void ViaMain::ForceExit()
{
	exit(0x00dead00);
}

/*--------------------------------------------------------------------------*/

void ViaMain::WriteLog(const char* file, int line, const char* func, int priority, const char* text)
{
	std::string f = ::GetFileFullname(file);

	time_t sectime;
	struct tm timeinfo;
	time(&sectime);
	localtime_s(&timeinfo, &sectime);

	char* letters = "IWEC";

	if (priority < 0)
	{
		priority = 0;
	}

	if (priority > 3)
	{
		priority = 3;
	}

	fprintf(m_log, "\"%02d:%02d:%02d$%c$%s$%s$%d$", timeinfo.tm_hour, timeinfo.tm_min, timeinfo.tm_sec, letters[priority], func, f.c_str(), line);

	int i = 0;

	while (text[i] != '\0')
	{
		if (text[i] == '\n')
		{
			fprintf(m_log, "<br />");
		}
		else
		{
			fprintf(m_log, "%c", text[i]);
		}

		++i;
	}

	fprintf(m_log, "\",\n");
}

/*--------------------------------------------------------------------------*/

void ViaMain::Loop()
{
	ViaMain::BenchmarkBegin();

	for (std::vector<struct FunctionCall>::iterator iter = m_functionsOrder.begin(); iter != m_functionsOrder.end(); ++iter)
	{
		ViaMain::Timestamp("Main loop - call %s with %d", iter->m_description.c_str(), iter->m_arg);
		iter->m_mod->OnLoop(iter->m_arg);
	}

	ViaMain::BenchmarkEnd();
}

/*--------------------------------------------------------------------------*/

void ViaMain::LogCallOnLoopEvents()
{
	std::stringstream ss;
	ss << "CallOnLoop Events:\n";

	for (std::vector<struct FunctionCall>::iterator iter = m_functionsOrder.begin(); iter != m_functionsOrder.end(); ++iter)
	{
		ss << iter->m_mod->GetModuleName() << " index:" << iter->m_index << " arg:" << iter->m_arg << " desc:" << iter->m_description << "\n";
	}

	AssertLogI(true, ss.str().c_str());
}

/*--------------------------------------------------------------------------*/

void ViaMain::Timestamp(const char* format, ...)
{
	if (ViaMain::m_benchmarkEnabled)
	{
		ViaMain::m_benchmarkCriticalSection->Lock(0);

		char buffer[128];

		va_list args;
		va_start(args, format);
		vsprintf(buffer, format, args);
		perror(buffer);
		va_end(args);

		std::cout << std::fixed << std::setw(8) << std::setprecision(5) << std::setfill('0')
		          << ViaMain::m_benchmarkTimer.GetTimer() << " ms |   " << buffer << "\n";

		ViaMain::m_benchmarkCriticalSection->Unlock(0);
	}
}

/*--------------------------------------------------------------------------*/

void ViaMain::StartBenchmark()
{
	AssertLogC(ViaMain::m_benchmarkNextFrame == true || ViaMain::m_benchmarkEnabled, "Benchmark already started");
	ViaMain::m_benchmarkNextFrame = true;
}

/*--------------------------------------------------------------------------*/

void ViaMain::BenchmarkBegin()
{
	if (ViaMain::m_benchmarkNextFrame)
	{
		ViaMain::m_benchmarkTimer.SetTimer();

		ViaMain::m_benchmarkNextFrame = false;
		ViaMain::m_benchmarkEnabled = true;
		ViaMain::Timestamp("Benchmark started");
	}
}

/*--------------------------------------------------------------------------*/

void ViaMain::BenchmarkEnd()
{
	if (m_benchmarkEnabled)
	{
		ViaMain::Timestamp("Benchmark finished");
		ViaMain::m_benchmarkEnabled = false;
	}
}

/*--------------------------------------------------------------------------*/
